local template = require "plugins.common.template"

local redis = require("plugins.common.redis")
local redis_cluster = require("plugins.common.redis_cluster")
--local new_tab = require("table.new")
local ipmatcher = require("resty.ipmatcher")
local ngx = ngx
local limit_dict = ngx.shared.frequency_limit
local limit_black_dict = ngx.shared.limit_black_dict
local core = require("core")
local plugin_name = "frequencylimit"
local log = core.log.new(plugin_name..": ")
local app_config = require("conf.config").get(plugin_name)
local util = require "plugins.common.util"
local request_matcher = require "plugins.filter.request_matcher".new(app_config["header_ip_args"],app_config["app_key"])
local limit_black_time = app_config["limit_black_time"] or 120
local plugin_config = require("conf.config").get_plugin(plugin_name)
local status_code = app_config["status_code"];
local msg = app_config["msg"]
local allow_ips
--local allow_ips = new_tab(0,200) ;
--local black_ips = new_tab(0,200) ;
local black_ips
local get_var = require("resty.ngxvar").fetch

local ngx_re = require "ngx.re"

local vars = ngx.var

do
    --for _,ip in ipairs( app_config["allow_ips"] or {}) do
    --    allow_ips[ip] = ""
    --end
    --for _,ip in ipairs( app_config["black_ips"] or {}) do
    --    black_ips[ip] = ""
    --end
    allow_ips = ipmatcher.new(app_config["allow_ips"] or {})
    black_ips = ipmatcher.new(app_config["black_ips"] or {})
end

local red = nil
local red_prefix = app_config["redis_prefix"] or ""
-- optional 是否可选
local _M = {
    name = plugin_name,
    desc = "frequency limit filter",
    optional = false,
    version = "v1.0"
}

function _M.do_in_init_worker()
    --log.info("do_in_init_worker")
    -- body
    -- 如果redis为单机模式
    local redis_type = app_config["type"] or "local"
    if redis_type == "redis" then
        local opts = {
            --ip = config.get("app.redis_ip"),
            ip = app_config["redis_ip"],
            --port = config.get("app.redis_port"),
            port = app_config["redis_port"],
            --password = config.get("app.redis_pass"),
            password = app_config["redis_pass"],
            --db_index = config.get("app.redis_db"),
            db_index = app_config["redis_db"],
            --timeout = config.get("app.redis_timeout"),
            timeout = app_config["redis_timeout"],
            --pool_size = config.get("app.redis_pool_size"),
            pool_size = app_config["redis_pool_size"],
        }
        red = redis:new(opts)
    elseif redis_type == "redis_cluster" then
        local opts = {
            serv_list = {},
            pool_max_idle_time = app_config["pool_max_idle_time"],
            pool_size =  app_config["redis_pool_size"],
            timeout = app_config["redis_timeout"],
            max_redirection = app_config["max_redirection"],
            max_connection_attempts = app_config["max_connection_attempts"],
            pass = app_config["redis_pass"],
            dict_name = app_config["dict_name"]
        }
        for i, conf_item in ipairs(app_config["redis_serv_list"]) do
            local res,err = ngx_re.split(conf_item,":")
            --local host = res[1]
            --local port = res[2]
            --ngx.log(ngx.ERR,host .. port ..  i)
            opts.serv_list[i] = { ip = res[1] ,port = res[2]}
        end
        --ngx.log(ngx.ERR,opts.pool_size)
        red = redis_cluster:new(opts)
    end
end

--local function get_ip_access_count(obj,key)
--    return obj:get(key)
--end

local script = util.compress_script([=[
    local ttl = redis.call('ttl', KEYS[1])
    if ttl < 0 then
        redis.call('set', KEYS[1], ARGV[1]-1, 'EX', ARGV[2])
        return ARGV[1]-1
    end
    return redis.call('incrby', KEYS[1], -1)
]=])

--local function set_ip_access_init(obj,key,time)
--    local count_now
--    if red == nil then
--        count_now = obj:incr(key, 1,0)
--    else
--        count_now = obj:incr(key)
--    end
--    obj:expire(key,tonumber(time))
--    log.error("11111-",count_now)
--    return count_now
--end


function _M.do_in_access()
    local client_ip = util.get_client_ip(request_matcher.get_ip_mode);

    if  black_ips:match(client_ip) then
        --log.error(black_ips[client_ip])
        ngx.ctx.is_frequency_limit = true
        return template:render(msg, status_code);
    end

    if allow_ips:match(client_ip) then
        return
    end
    --log.error(client_ip,table.concat(black_ips))
    --if  black_ips[client_ip] then
    --    --log.error(black_ips[client_ip])
    --    ngx.ctx.is_frequency_limit = true
    --    return template:render(msg, status_code);
    --end
    --if allow_ips[client_ip]  then
    --    --log.error(allow_ips[client_ip])
    --    return
    --end
    --for _,ip in ipairs(black_ips) do
    --    if client_ip == ip then
    --        ngx.ctx.is_frequency_limit = true
    --        --return template:render("common","frequency_limit", status_code);
    --        return template:render(msg, status_code);
    --    end
    --end
    --for _,ip in ipairs(allow_ips) do
    --    if client_ip == ip then
    --        return
    --    end
    --end
    for idx ,rule in ipairs(plugin_config) do
        local enable = rule['enable']
        local matcher = rule['matchers']
        --log.error(request_matcher:test(matcher))
        --log.error(request_matcher:test(matcher) == true)
        if enable == true and request_matcher:test(matcher) == true then
            local key = idx
            --log.error(util:get_client_ip())
            for _, item in ipairs(rule['limit_items']) do
                if item == 'ip' then
                    if client_ip == nil then
                        log.WARN("Frequency Limit：can't find the IP from the header")
                        return;
                    end
                    key = key .. '-' .. client_ip
                elseif item == 'uri' then
                    key = key .. '-' .. get_var("uri")

                elseif string.find(item, "param=") == 1 then
                    local keys = item:split("=");
                    local args_table = util.get_request_args()
                    if args_table[keys[2]] ~= nil then
                        key = key .. '-' .. args_table[keys[2]]
                    end
                elseif item == 'token' then
                    local header_table = ngx.req.get_headers()
                    if header_table["token"] ~= nil then
                        key = key .. '-' .. header_table["token"]
                    end

                elseif item == 'host' then
                    key = key .. '-' .. get_var("http_host")
                elseif item == 'app' then
                    key = key .. '-' .. get_var(request_matcher.app_key)
                elseif item == 'location' then
                    key = key .. '-' .. get_var("location")
                end
            end
            if key == idx or key == '' or key == nil then
                return;
            end
            --log.error(key)
            local time = rule['time']
            local count = rule['count']

            if limit_black_dict:get(key) ~= nil then
                --log.error(1111111)
                ngx.ctx.is_frequency_limit = true
                --return template:render("common","frequency_limit", status_code);
                return template:render(msg, status_code);
            end
            if tonumber(count) == 0 then
                limit_black_dict:set(key,"", tonumber(limit_black_time))
                ngx.ctx.is_frequency_limit = true
                --return template:render("common","frequency_limit", status_code);
                return template:render(msg, status_code);
            end

            local count_now
            if red == nil then
                --log.error( "key=" .. key);
                local ttl = limit_dict:ttl(key)
                if not ttl or ttl <= 0 then
                    count_now = limit_dict:incr(key, -1,tonumber(count),tonumber(time))
                else
                    count_now = limit_dict:incr(key, -1)
                end
            else
                --log.error( "else key=" .. key);
                count_now = red:eval(script, 1, key, tonumber(count),tonumber(time)) or 1
            end
            --local count_now = get_ip_access_count(red or limit_dict,key)
            --if red == nil  then
            --    count_now = limit_dict:get(key)
            --else
            --    count_now = red:get(key)
            --end

            --if count_now == nil then
            --    if tonumber(time) <= 0 then
            --        time = 1;
            --    end
            --    if red == nil then
            --        count_now = limit_dict:incr(key, 1,0,tonumber(time))
            --    else
            --        red:init_pipeline()
            --        red:incr(key)
            --        red:expire(key,tonumber(time))
            --        local results,err = red:commit_pipeline()
            --        if not results then
            --            -- 如果设置redis失败，直接返回不走后面的逻辑
            --            log.error("---",err)
            --            return
            --        else
            --            count_now = results[1]
            --            if type(count_now) == "table" then
            --                log.error(count_now[2])
            --                count_now = 1
            --            end
            --        end
            --        --count_now = results[1] or 1
            --        --log.error("---",count_now)
            --    end
            --    --count_now = set_ip_access_init(red or limit_dict,key,tonumber(time))
            --    --if red == nil then
            --    --    --limit_dict:set(key, 1,tonumber(time))
            --    --    --log.error("11111",count_now)
            --    --    count_now = limit_dict:incr(key, 1,0)
            --    --    limit_dict:expire(key,tonumber(time))
            --    --    log.error("11111-",count_now)
            --    --    --limit_dict:expire(key,tonumber(time))
            --    --
            --    --else
            --    --    count_now = red:incr(key)
            --    --    log.error("11111-",count_now)
            --    --    red:expire(key,tonumber(time))
            --    --end
            --    --count_now = 1
            --else
            --    if red == nil then
            --        count_now = limit_dict:incr(key, 1)
            --    else
            --        -- 如果redis出现超时，使用之前的值，否则再下面比较时会出现500的错误
            --        count_now = red:incr(key) or count_now
            --    end
            --
            --end
            --log.error(type(count_now))
            if tonumber(count_now) < 0 then

                if app_config["debug_model"] == true then
                    return;
                end
                limit_black_dict:set(key,"", tonumber(limit_black_time))
                log.warn("Frequency Limit：too many requests，key=" .. key);
                ngx.ctx.is_frequency_limit = true
                --prometheus.metric_limit_request_bytes:inc(tonumber(ngx.var.request_length),{ngx.var.http_host});
                --prometheus.metric_limit_requests:inc(1,{ngx.var.http_host,ngx.var.location});
                --return template:render("common","frequency_limit", status_code);
                return template:render(msg, status_code);
            end

            -- 只要有规则匹配即停止此规则后续的规则匹配，为了保障openresty的性能，如果不匹配即停止的话每多匹配上一条性能就会损耗15%左右
            return
        end

    end

end


setmetatable(_M,{
    __tostring = function(self)
        return self.name
    end
})

return _M